//===--- PILGenDestructor.cpp - PILGen for destructors --------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/pil/gen/PILGenFunction.h"
#include "polarphp/pil/gen/RValue.h"
#include "polarphp/pil/gen/ArgumentScope.h"
#include "polarphp/ast/GenericSignature.h"
#include "polarphp/ast/SubstitutionMap.h"
#include "polarphp/pil/lang/TypeLowering.h"

using namespace polar;
using namespace lowering;

void PILGenFunction::emitDestroyingDestructor(DestructorDecl *dd) {
   MagicFunctionName = DeclName(SGM.M.getAstContext().getIdentifier("deinit"));

   RegularLocation Loc(dd);
   if (dd->isImplicit())
      Loc.markAutoGenerated();

   auto cd = cast<ClassDecl>(dd->getDeclContext());
   PILValue selfValue = emitSelfDecl(dd->getImplicitSelfDecl());

   // Create a basic block to jump to for the implicit destruction behavior
   // of releasing the elements and calling the superclass destructor.
   // We won't actually emit the block until we finish with the destructor body.
   prepareEpilog(Type(), false, CleanupLocation::get(Loc));

   emitProfilerIncrement(dd->getBody());
   // Emit the destructor body.
   emitStmt(dd->getBody());

   Optional<PILValue> maybeReturnValue;
   PILLocation returnLoc(Loc);
   std::tie(maybeReturnValue, returnLoc) = emitEpilogBB(Loc);

   if (!maybeReturnValue)
      return;

   auto cleanupLoc = CleanupLocation::get(Loc);

   // If we have a superclass, invoke its destructor.
   PILValue resultSelfValue;
   PILType objectPtrTy = PILType::getNativeObjectType(F.getAstContext());
   PILType classTy = selfValue->getType();
   if (cd->hasSuperclass()) {
      Type superclassTy = dd->mapTypeIntoContext(cd->getSuperclass());
      ClassDecl *superclass = superclassTy->getClassOrBoundGenericClass();
      auto superclassDtorDecl = superclass->getDestructor();
      PILDeclRef dtorConstant =
         PILDeclRef(superclassDtorDecl, PILDeclRef::Kind::Destroyer);
      PILType basePILTy = getLoweredLoadableType(superclassTy);
      PILValue baseSelf = B.createUpcast(cleanupLoc, selfValue, basePILTy);
      ManagedValue dtorValue;
      PILType dtorTy;
      auto subMap
         = superclassTy->getContextSubstitutionMap(SGM.M.getPolarphpModule(),
                                                   superclass);
      std::tie(dtorValue, dtorTy)
         = emitSiblingMethodRef(cleanupLoc, baseSelf, dtorConstant, subMap);

      resultSelfValue = B.createApply(cleanupLoc, dtorValue.forward(*this),
                                      subMap, baseSelf);
   } else {
      resultSelfValue = selfValue;
   }

   ArgumentScope S(*this, Loc);
   ManagedValue borrowedValue =
      ManagedValue::forUnmanaged(resultSelfValue).borrow(*this, cleanupLoc);

   if (classTy != borrowedValue.getType()) {
      borrowedValue =
         B.createUncheckedRefCast(cleanupLoc, borrowedValue, classTy);
   }

   // Release our members.
   emitClassMemberDestruction(borrowedValue, cd, cleanupLoc);

   S.pop();

   if (resultSelfValue->getType() != objectPtrTy) {
      resultSelfValue =
         B.createUncheckedRefCast(cleanupLoc, resultSelfValue, objectPtrTy);
   }
   if (resultSelfValue.getOwnershipKind() != ValueOwnershipKind::Owned) {
      assert(resultSelfValue.getOwnershipKind() ==
             ValueOwnershipKind::Guaranteed);
      resultSelfValue = B.createUncheckedOwnershipConversion(
         cleanupLoc, resultSelfValue, ValueOwnershipKind::Owned);
   }
   B.createReturn(returnLoc, resultSelfValue);
}

void PILGenFunction::emitDeallocatingDestructor(DestructorDecl *dd) {
   MagicFunctionName = DeclName(SGM.M.getAstContext().getIdentifier("deinit"));

   // The deallocating destructor is always auto-generated.
   RegularLocation loc(dd);
   loc.markAutoGenerated();

   // Emit the prolog.
   PILValue initialSelfValue = emitSelfDecl(dd->getImplicitSelfDecl());

   // Form a reference to the destroying destructor.
   PILDeclRef dtorConstant(dd, PILDeclRef::Kind::Destroyer);
   auto classTy = initialSelfValue->getType();
   auto classDecl = classTy.getAstType()->getAnyNominal();
   ManagedValue dtorValue;
   PILType dtorTy;
   auto subMap = classTy.getAstType()
      ->getContextSubstitutionMap(SGM.M.getPolarphpModule(),
                                  classDecl);
   std::tie(dtorValue, dtorTy)
      = emitSiblingMethodRef(loc, initialSelfValue, dtorConstant, subMap);

   // Call the destroying destructor.
   PILValue selfForDealloc;
   {
      FullExpr CleanupScope(Cleanups, CleanupLocation::get(loc));
      ManagedValue borrowedSelf = emitManagedBeginBorrow(loc, initialSelfValue);
      selfForDealloc = B.createApply(loc, dtorValue.forward(*this), subMap,
                                     borrowedSelf.getUnmanagedValue());
   }

   // Balance out the +1 from the self argument using end_lifetime.
   //
   // The issue here is that:
   //
   // 1. Self is passed into deallocating deinits at +1.
   // 2. Destroying deinits take in self as a +0 value that is then returned at
   // +1.
   //
   // This means that the lifetime of self can not be modeled statically in a
   // deallocating deinit without analyzing the body of the destroying deinit
   // (something that violates semantic sil). Thus we add an artificial destroy of
   // self before the actual destroy of self so that the verifier can understand
   // that self is being properly balanced.
   B.createEndLifetime(loc, initialSelfValue);

   // Deallocate the object.
   selfForDealloc = B.createUncheckedRefCast(loc, selfForDealloc, classTy);
   B.createDeallocRef(loc, selfForDealloc, false);

   emitProfilerIncrement(dd->getBody());

   // Return.
   B.createReturn(loc, emitEmptyTuple(loc));
}

void PILGenFunction::emitIVarDestroyer(PILDeclRef ivarDestroyer) {
   auto cd = cast<ClassDecl>(ivarDestroyer.getDecl());
   RegularLocation loc(cd);
   loc.markAutoGenerated();

   ManagedValue selfValue = ManagedValue::forUnmanaged(
      emitSelfDecl(cd->getDestructor()->getImplicitSelfDecl()));

   auto cleanupLoc = CleanupLocation::get(loc);
   prepareEpilog(TupleType::getEmpty(getAstContext()), false, cleanupLoc);
   {
      Scope S(*this, cleanupLoc);
      emitClassMemberDestruction(selfValue, cd, cleanupLoc);
   }

   B.createReturn(loc, emitEmptyTuple(loc));
   emitEpilog(loc);
}

void PILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
                                                ClassDecl *cd,
                                                CleanupLocation cleanupLoc) {
   selfValue = selfValue.borrow(*this, cleanupLoc);
   for (VarDecl *vd : cd->getStoredProperties()) {
      const TypeLowering &ti = getTypeLowering(vd->getType());
      if (!ti.isTrivial()) {
         PILValue addr =
            B.createRefElementAddr(cleanupLoc, selfValue.getValue(), vd,
                                   ti.getLoweredType().getAddressType());
         B.createDestroyAddr(cleanupLoc, addr);
      }
   }
}


void PILGenFunction::emitObjCDestructor(PILDeclRef dtor) {
   auto dd = cast<DestructorDecl>(dtor.getDecl());
   auto cd = cast<ClassDecl>(dd->getDeclContext());
   MagicFunctionName = DeclName(SGM.M.getAstContext().getIdentifier("deinit"));

   RegularLocation loc(dd);
   if (dd->isImplicit())
      loc.markAutoGenerated();

   PILValue selfValue = emitSelfDecl(dd->getImplicitSelfDecl());

   // Create a basic block to jump to for the implicit destruction behavior
   // of releasing the elements and calling the superclass destructor.
   // We won't actually emit the block until we finish with the destructor body.
   prepareEpilog(Type(), false, CleanupLocation::get(loc));

   emitProfilerIncrement(dd->getBody());
   // Emit the destructor body.
   emitStmt(dd->getBody());

   Optional<PILValue> maybeReturnValue;
   PILLocation returnLoc(loc);
   std::tie(maybeReturnValue, returnLoc) = emitEpilogBB(loc);

   if (!maybeReturnValue)
      return;

   auto cleanupLoc = CleanupLocation::get(loc);

   // Note: the ivar destroyer is responsible for destroying the
   // instance variables before the object is actually deallocated.

   // Form a reference to the superclass -dealloc.
   Type superclassTy = dd->mapTypeIntoContext(cd->getSuperclass());
   assert(superclassTy && "Emitting Objective-C -dealloc without superclass?");
   ClassDecl *superclass = superclassTy->getClassOrBoundGenericClass();
   auto superclassDtorDecl = superclass->getDestructor();
   auto superclassDtor = PILDeclRef(superclassDtorDecl,
                                    PILDeclRef::Kind::Deallocator)
      .asForeign();
   auto superclassDtorType =
      SGM.Types.getConstantType(getTypeExpansionContext(), superclassDtor);
   PILValue superclassDtorValue = B.createObjCSuperMethod(
      cleanupLoc, selfValue, superclassDtor,
      superclassDtorType);

   // Call the superclass's -dealloc.
   PILType superclassPILTy = getLoweredLoadableType(superclassTy);
   PILValue superSelf = B.createUpcast(cleanupLoc, selfValue, superclassPILTy);
   assert(superSelf.getOwnershipKind() == ValueOwnershipKind::Owned);

   auto subMap
      = superclassTy->getContextSubstitutionMap(SGM.M.getPolarphpModule(),
                                                superclass);

   B.createApply(cleanupLoc, superclassDtorValue, subMap, superSelf);

   // We know that the givne value came in at +1, but we pass the relevant value
   // as unowned to the destructor. Create a fake balance for the verifier to be
   // happy.
   B.createEndLifetime(cleanupLoc, superSelf);

   // Return.
   B.createReturn(returnLoc, emitEmptyTuple(cleanupLoc));
}
